package DEEPERsource.DEEPERsource.source.corpus;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.naming.OperationNotSupportedException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import parser.Parser;
import preprocessing.Preprocessor;

import deeper.Direction;
import deeper.Interaction;
import deeper.InteractionType;
import deeper.Protein;
import exceptions.GraphException;
import exceptions.NoSuchWordException;

/**
 * Leaf class of the Composite pattern. COncentrate the most useful fuctionality
 * of the application.
 * 
 * @author tfayruzo
 * 
 */
public class Sentence extends Text {
	private static Log _log = LogFactory.getLog(Sentence.class);

	public String text;

	// fields that should be filled during corpus construction
	private List<Protein> proteins;
	
	private List<Protein> nestedProteins;

	private List<Interaction> interactions;

	// preprocessing data. Preprocessing step is neccessary, because
	// it assigns unique ids to tagged proteins. This allows to avoid confusion
	// if proteins are mentioned in the sentence several times.
	// private Preprocessor prep = new SimplePreprocessor();
	// private Map<String, String> abbr;

	public Sentence(String text, String id, String origId) {
		this.id = id;
		this.originalId = origId;
		this.text = text;
		proteins = new ArrayList<Protein>();
		interactions = new ArrayList<Interaction>();
	}

	@Override
	public Collection<Interaction> interactions() {
		return interactions;
	}

	@Override
	public void parse(Parser parser, String... params) {
		_log.info(text);
		boolean load = false;
		String path = "";
		for (int i = 0; i < params.length; i++) {
			if (params[i].equals("serialized")) {
				load = true;
				path = params[++i];
			}
		}

		//List<Interaction> deserialized = new ArrayList<Interaction>();

		for (Iterator<Interaction> it = interactions.iterator(); it.hasNext();) {
			// sentence parse could be taken from the file
			// or be parsed directly by parser, dependent on provided params
			Interaction i = it.next();
			//special case of interactions, that defined to be negative by default
			if(i.predefinedStatus != null){
				i.status = i.predefinedStatus;
				continue;
			}
			if (load) {
				try {
					ObjectInputStream in = new ObjectInputStream(
							new FileInputStream(path.concat("/").concat(i.id)));
					Interaction full = (Interaction) in.readObject();
					//saving memory
					//i.parseTree = full.parseTree;
					//i.depTree = full.depTree;
					//i.struct = full.struct;
					i.depPath = full.depPath;
					i.parsePath = full.parsePath;
				} catch (UnsupportedOperationException e) {
					_log.error(e.getMessage());
					return;
				} catch (FileNotFoundException e) {
					e.printStackTrace();
				} catch (IOException e) {
					e.printStackTrace();
				} catch (ClassNotFoundException e) {
					e.printStackTrace();
				}
			} else {
				try {
					parser.parse(i.sentence);
					i.parseTree = parser.getParseTree();
					i.depTree = parser.getDependencyTree();
					i.struct = parser.getStructuredSentence();
					i.initInteraction();
				} catch (NoSuchWordException e) {
					_log.error(e.getMessage());
					it.remove();
				} catch (GraphException e) {
					_log.error("Can't define path for interaction between "
							+ i.p1.name + " and " + i.p2.name);
					_log.error("Nested exception:" + e.getClass() + ":"
							+ e.getMessage());
					it.remove();
				} catch (Exception e1) {
					_log.error(e1.getMessage());
					return;
				}
			}
		}
		/*if (load) {
			interactions = deserialized;
		}*/
	}

	@Override
	public void add(Text text) throws OperationNotSupportedException {
		throw new OperationNotSupportedException(
				"Sentence could not act as a container");
	}

	// -------------NON-INTERFACE PUBLIC METHODS-----------

	public void addProtein(Protein p) {
		proteins.add(p);
	}

	public Protein getProtein(String id) {
		for (Protein p : proteins) {
			if (p.id.equals(id)) {
				return p;
			}
		}
		return null;
	}

	public void addInteraction(Interaction i) {
		interactions.add(i);
	}

	int interId = 0;

	public void populateInteractions(Preprocessor prep) {
		//populating nested proteins array
		nestedProteins = new ArrayList<Protein>();
		for(Protein pp1 : proteins){
			for(Protein pp2 : proteins){
				if(!pp1.equals(pp2))
					if(pp1.includes(pp2)){
						nestedProteins.add(pp2);
					}
			}
		}
		interId = interactions.size();
		for (int i = 0, size = proteins.size(); i < size - 1; i++) {
			for (int j = i + 1; j < size; j++) {
				//cloning is necessary, because proteins list is shared among different interactions
				//and char offsets for each interaction are different
				List<Protein> localProteins = new ArrayList<Protein>();
				for(Protein p : proteins)
					localProteins.add((Protein)p.clone());
				Protein p1 = (Protein) localProteins.get(i);
				Protein p2 = (Protein) localProteins.get(j);
				//check if one of the proteins is nested, then it is a special type of interaction
				if(nestedProteins.contains(p1)||nestedProteins.contains(p2)){
					Interaction inter = getInteraction4Nested(p1, p2);
					inter.sentence = text;
					inter.sentId = id;
					inter.origSentId = originalId;
					continue;
				}
				
				/*if (p1.startOffset[0] <= p2.startOffset[0]
						&& p2.endOffset[p2.endOffset.length - 1] <= p1.endOffset[p1.endOffset.length - 1]
						|| p2.startOffset[0] <= p1.startOffset[0]
						&& p1.endOffset[p1.endOffset.length - 1] <= p2.endOffset[p2.endOffset.length - 1]) {
					_log.error("Proteins "+ p1.name+ " and "+ p2.name+ " are nested, and interaction between them couldn't be established.");
					continue;
				}*/
				
				Set<Protein> pSet = new HashSet<Protein>();
				pSet.add(p1);
				pSet.add(p2);
				_log.debug("Processing protein pair " + p1.name + " - "	+ p2.name);
				localProteins.removeAll(nestedProteins);
				Map<String, String> prepSent = prep.preprocessSentence(text, pSet, localProteins);
				String prepText = prepSent.get("sentence");
				prepSent.remove("sentence");
				Interaction inter = getInteraction(p1, p2);
				inter.abbr = prepSent;
				inter.sentence = prepText;
				inter.sentId = id;
				inter.origSentId = originalId;
				//statistics code
				/*for(Protein pp1 : proteins){
					for(Protein pp2 : pSet){
						if(!pp1.equals(pp2))
							if(pp1.includes(pp2)){
								System.out.println(inter.id);
							}
					}
				}*/
				//end statistics code
			}
		}
	}

	// -------------------PRIVATE METHOS-------------------
	
	private Interaction getInteraction4Nested(Protein p1, Protein p2){
		Interaction i = new Interaction();
		i.p1 = p1;
		i.p2 = p2;
		i.predefinedStatus = InteractionType.FALSE;
		i.id = id+".i"+interactions.size();
		i.type = "no";
		interactions.add(i);
		return i;
	}

	private Interaction getInteraction(Protein p1, Protein p2) {
		List<Protein> pList = new ArrayList<Protein>();
		pList.add(p1);
		pList.add(p2);
		// first try to find the protein pair among positive instances
		for (Interaction i : interactions) {
			if (pList.contains(i.p1) && pList.contains(i.p2)) {
				i.p1 = pList.get(pList.indexOf(i.p1));
				i.p2 = pList.get(pList.indexOf(i.p2));
				return i;
			}
		}		
		// if it's not found, then return a negative instance
		Interaction i = new Interaction();
		i.p1 = p1;
		i.p2 = p2;
		i.status = InteractionType.FALSE;
		i.id = id+".i"+interactions.size();
		i.type = "no";
		interactions.add(i);
		return i;
	}
}
